<template>
  <div class="my-scene-box" ref="mySceneBox"></div>
</template>

<script>
// 导入整个 three.js核心库
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import WebGL from "@/utils/WebGL.js";

export default {
  name: "demoOne",
  data() {
    return {
      scene: null, // 场景
      renderer: null, // 渲染器
      controls: null, // 轨道控制器
    };
  },
  mounted() {
    this.initThree();
  },
  methods: {
    initThree() {
      // 浏览器WebGL兼容性判断
      if (!WebGL.isWebGLAvailable()) {
        const warning = WebGL.getWebGLErrorMessage();
        this.$refs.mySceneBox.appendChild(warning);
        return;
      }
      // 1.创建场景对象Scene
      this.scene = new THREE.Scene();
      // 2.创建多个几何模型 并生产 mesh(网格模型) 数组
      const meshList = this.initGeometry();

      // 场景对象scene的方法.add()把网格模型mesh加入场景中
      meshList.forEach((mesh) => {
        this.scene.add(mesh); //网格模型添加到场景中  默认是在 （0，0，0）坐标
      });

      // 辅助坐标系  参数250表示坐标系大小，可以根据场景大小去设置
      // var axisHelper = new THREE.AxisHelper(250); // 构造函数改名了
      const axisHelper = new THREE.AxesHelper(250);
      this.scene.add(axisHelper);

      // 4.光源设置
      const light = this.initLight();
      this.scene.add(light);

      // 5.相机设置
      var width = this.$refs.mySceneBox.clientWidth; //窗口宽度
      var height = this.$refs.mySceneBox.clientHeight; //窗口高度
      var k = width / height; //窗口宽高比
      var s = 400; //三维场景显示范围控制系数，系数越大，显示的范围越大
      //创建相机对象
      this.camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);
      this.camera.position.set(200, 500, 500); //设置相机位置
      this.camera.lookAt(this.scene.position); //设置相机方向(指向的场景对象)
      // 6.创建渲染器对象
      this.renderer = new THREE.WebGLRenderer();
      this.renderer.setSize(width, height); //设置渲染区域尺寸
      this.renderer.setClearColor(0xb9d3ff, 1); //设置背景颜色

      this.$refs.mySceneBox.appendChild(this.renderer.domElement); //body元素中插入canvas对象

      //执行渲染操作   指定场景、相机作为参数
      this.loopRender();

      // 新增轨道控制器
      this.controls = new OrbitControls(this.camera, this.renderer.domElement); //创建控件对象
    },
    // 生成光源
    initLight() {
      /** 光源分类
       * 1.AmbientLight      环境光
       * 2.PointLight        点光源
       * 3.DirectionalLight  平行光（比如太阳光）
       * 4.SpotLight         聚光源
       *
       * 立体效果:仅仅使用环境光的情况下，你会发现整个立方体没有任何棱角感，这是因为环境光只是设置整个空间的明暗效果。如果需要立方体渲染要想有立体效果，需要使用具有方向性的点光源、平行光源等。
       */
      // 1.环境光    环境光颜色与网格模型的颜色进行RGB进行乘法运算
      // const ambient = new THREE.AmbientLight(0x444444);
      // const ambient = new THREE.AmbientLight(0xffffff);   // 这个更亮
      // return ambient;

      // 2.点光源  可以设置多个点光源
      //点光源
      const point = new THREE.PointLight(0xffffff);
      point.position.set(400, 200, 300); //点光源位置
      return point;
    },
    // 创建多个几何模型  返回 mesh 对象数组
    initGeometry() {
      const returnList = [];
      var geometry2 = new THREE.BoxGeometry(100, 100, 100);
      // 三角形面渲染模式
      var material2 = new THREE.MeshLambertMaterial({
        color: 0x0000ff, //三角面颜色
      }); //材质对象
      var mesh = new THREE.Mesh(geometry2, material2); //网格模型对象Mesh

      /**
       * 点模型Points、线模型Line、网格网格模型Mesh等模型对象的基类都是Object3D，
       * 如果想对这些模型进行旋转、缩放、平移等操作，如何实现，可以查询Threejs文档Object3D对相关属性和方法的介绍。
       */

      // 1. scale 缩放
      mesh.scale.set(0.5, 1.5, 2); // 网格模型xyz方向分别缩放0.5,1.5,2倍
      mesh.scale.x = 2.0; //  x轴方向放大2倍

      // 2. position 位置       模型位置.position的默认值是THREE.Vector3(0.0,0.0,0.0)。
      mesh.position.set(100, 0, 0); // 设置模型xyz坐标
      mesh.position.x = 150; // 设置模型x坐标

      // 3. 平移    网格模型沿着x轴正方向平移100，可以多次执行该语句，每次执行都是相对上一次的位置进行平移变换。
      //            执行.translateX()、.translateY()、.translateOnAxis()等方法本质上改变的都是模型的位置属性.position。
      //等价于mesh.position = mesh.position + 100;
      mesh.translateX(100); //沿着x轴正方向平移距离100
      mesh.translateY(-100); //沿着y轴正方向平移距离100
      mesh.translateZ(100); //沿着z轴正方向平移距离100

      // 沿着自定义的方向移动。
      //向量Vector3对象表示方向
      var axis = new THREE.Vector3(1, 1, 1);
      axis.normalize(); //向量归一化
      //沿着axis轴表示方向平移100
      mesh.translateOnAxis(axis, 100);
      console.log("mesh.position", mesh.position);

      /**
       *  4.旋转
       *  4.1 立方体网格模型绕立方体的x轴旋转π/4，可以多次执行该语句，每次执行都是相对上一次的角度进行旋转变化
       *  4.2 执行旋转.rotateX()等方法和执行平移.translateY()等方法一样都是对模型状态属性的改变，区别在于执行平移方法改变的是模型的位置属性.position，
       *      执行模型的旋转方法改变的是表示模型角度状态的角度属性.rotation或者四元数属性.quaternion。
       *  4.3 模型的角度属性.rotation和四元数属性.quaternion都是表示模型的角度状态，只是表示方法不同，.rotation属性值是欧拉对象Euler,.quaternion属性值是是四元数对象Quaternion
       */
      mesh.rotateX(Math.PI / 4); //绕x轴旋转π/4
      // 网格模型绕(0,1,0)向量表示的轴旋转π/8
      var axis2 = new THREE.Vector3(0, 1, 0); //向量axis
      mesh.rotateOnAxis(axis2, Math.PI / 8); //绕axis轴旋转π/8
      console.log("mesh.rotation", mesh.rotation);
      returnList.push(mesh);

      /**   总结:  材质和模型对象对应关系
       *    使用对应关系
       *    点模型Points、线模型Line、网格网格模型Mesh都是由几何体Geometry和材质Material构成，这三种模型的区别在于对几何体顶点数据的渲染方式不同，
       *
       *  点模型：Points
       *  渲染模式：
       *        点渲染模式：点模型Points就是几何体的每一个顶点数据渲染为一个方形区域，方形区域的大小可以设置。
       *  点材质：PointsMaterial       点材质对象
       *
       *  线模型：Line、LineLoop、LineSegments
       *  渲染模式：线渲染模式
       *  线材质:
       *          LineBasicMaterial    直线基础材质对象
       *          LineDashedMaterial   虚线材质对象
       *
       *  网格模型：Mesh、SkinnedMesh（骨骼网格模型）
       *  渲染模式：
       *         线渲染模式： 线模型除了Line还有LineLoop和LineSegments,LineLoop和Line区别是连线的时候会闭合把第一个顶点和最后一个顶点连接起来，
       *                    LineSegments则是顶点不共享，第1、2点确定一条线，第3、4顶点确定一条直线，第2和3点之间不连接
       *  网格材质：
       *          MeshBasicMaterial    基础网格材质对象
       *          MeshLambertMaterial  慢反射材质
       *          MeshPhongMaterial    高光反射材质
       *          PBR材质（比MeshPhongMaterial效果更好）：MeshStandardMaterial、MeshPhysicalMaterial
       *          MeshDepthMaterial    网格深度材质
       *          MeshNormalMaterial   网格法向量材质
       *
       *  精灵模型：Sprite
       *  精灵材质：SpriteMaterial       精灵Sprite材质
       *
       * 自定义着色器材质:
       *          RawShaderMaterial
       *          ShaderMaterial
       * */
      return returnList;
    },

    // 渲染函数
    loopRender() {
      requestAnimationFrame(this.loopRender);
      this.renderer.render(this.scene, this.camera); //执行渲染操作
    },
  },
};
</script>
<style>
.my-scene-box {
  width: 90%;
  height: 500px;
  margin: 0 auto;
  border: 1px solid #000;
}
</style>
